/**
 * 
 */
package com.celephais.common.server.model;

import java.util.List;

import javax.jdo.Query;
import javax.jdo.annotations.EmbeddedOnly;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;

import com.celephais.common.server.JDOPersistence.PMF;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

/**
 * @author Eugene Shen
 * A reference to a reference-able object.
 * In the database, only the actually-referenced class and the key field are
 * stored.  The human-readable name is merely filled in as the reference
 * is resolved.
 * The magic here is that by parameterizing a variable of this class, you
 * limit the classes of objects that it can possibly refer to.  Then, as
 * you create a new instance, you're required to pass in a Class that is
 * assignable to T, which forces you specify the exact class that instance
 * refers to.
 * 
 * Whenever you embed an instance of this class in your class, it is ALWAYS
 * a good idea to redefine the column names so they do not collide with a
 * new reference if you add another.  A simple naming scheme is just to add
 * the variable's name as a prefix.  For example, a reference called
 * "exercise" can be renamed as such:
 * 
 * @Embedded (members = {
 *		@Persistent(name="refClassName", columns=@Column(name="exerciseRefClassName")),
 *		@Persistent(name="refKey", columns=@Column(name="exerciseRefKey"))
 *	})
 */
@PersistenceCapable
// Allows us to not have a key, and forces classes that include us to use the
// @Embeded annotation.
@EmbeddedOnly 
public class Reference<T extends PersistentObject> {

	@Persistent
	private String refClassName;
	
	private Class<? extends T> refClass;
	
	@Persistent
	private Key refKey;
	
	private String refName;
	
	public Reference(Class<? extends T> refClass) {
		super();
		this.refClass = refClass;
		this.refClassName = refClass.getCanonicalName();
	}
	
	public Reference(Reference<T> rhs) {
		this(rhs.refClass);
		setKey(rhs.getKey());
	}
	
	public Reference(Class<? extends T> refClass, Key key) {
		this(refClass);
		setKey(key);
	}
	
	public Key getKey() {
		return refKey;
	}
	public void setKey(Key refKey) {
		this.refKey = refKey;
		
		PersistentObject obj = getReferencedObject();
		if (obj != null) {
			setName(obj.getName());
		}
		else {
			setName(null);
		}
	}
	
	public String getName() {
		return refName;
	}
	public void setName(String refName) {
		this.refName = refName;
	}
	
	/**
	 * Resolves the reference.  This method actually hits the datastore, so
	 * try not to call it too often.
	 * @return The actual object being referenced.
	 */
	public PersistentObject getReferencedObject() {
		if (refKey != null) {
			if (refClass == null) {
				try {
					// Technically, since refClass and refClassName are
					// private, this cast /should/ be safe.
					refClass = (Class<? extends T>)Class.forName(refClassName);
				}
				catch (ClassNotFoundException e) {
					e.printStackTrace();
					return null;
				}
			}
			
			Query query = PMF.getFactory().getPersistenceManager().newQuery(refClass);
			query.setFilter("key == '" + KeyFactory.keyToString(refKey) + "'");
			Object result = query.execute();
			if (result instanceof List<?>) {
				List<?> resultList = (List<?>) result;
				if (resultList.size() == 1) {
					Object singleRes = resultList.get(0);
					if (singleRes instanceof PersistentObject) {
						if (getName() == null) {
							setName(((PersistentObject)singleRes).getName());
						}
						return (PersistentObject)singleRes;
					}
				}
				else {
					System.out.println("Key-based select on " + refClassName + " with " + refKey + " returned " + resultList.size() + " results?!");
				}
			}
		}
		return null;
	}
}
